// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/builtins/builtins-utils-gen.h"
#include "src/builtins/builtins.h"
#include "src/code-stub-assembler.h"
#include "src/frame-constants.h"
#include "src/objects/api-callbacks.h"
#include "src/objects/descriptor-array.h"

#include "src/objects-inl.h" // weolar

namespace v8 {
namespace internal {

    TF_BUILTIN(FastFunctionPrototypeBind, CodeStubAssembler)
    {
        Label slow(this);

        // TODO(ishell): use constants from Descriptor once the JSFunction linkage
        // arguments are reordered.
        Node* argc = Parameter(Descriptor::kJSActualArgumentsCount);
        Node* context = Parameter(Descriptor::kContext);
        Node* new_target = Parameter(Descriptor::kJSNewTarget);

        CodeStubArguments args(this, ChangeInt32ToIntPtr(argc));

        // Check that receiver has instance type of JS_FUNCTION_TYPE
        Node* receiver = args.GetReceiver();
        GotoIf(TaggedIsSmi(receiver), &slow);

        Node* receiver_map = LoadMap(receiver);
        {
            Node* instance_type = LoadMapInstanceType(receiver_map);
            GotoIfNot(
                Word32Or(InstanceTypeEqual(instance_type, JS_FUNCTION_TYPE),
                    InstanceTypeEqual(instance_type, JS_BOUND_FUNCTION_TYPE)),
                &slow);
        }

        // Disallow binding of slow-mode functions. We need to figure out whether the
        // length and name property are in the original state.
        Comment("Disallow binding of slow-mode functions");
        GotoIf(IsDictionaryMap(receiver_map), &slow);

        // Check whether the length and name properties are still present as
        // AccessorInfo objects. In that case, their value can be recomputed even if
        // the actual value on the object changes.
        Comment("Check descriptor array length");
        TNode<DescriptorArray> descriptors = LoadMapDescriptors(receiver_map);
        // Minimum descriptor array length required for fast path.
        const int min_nof_descriptors = i::Max(JSFunction::kLengthDescriptorIndex,
            JSFunction::kNameDescriptorIndex);
        TNode<Int32T> nof_descriptors = LoadNumberOfDescriptors(descriptors);
        GotoIf(
            Int32LessThanOrEqual(nof_descriptors, Int32Constant(min_nof_descriptors)),
            &slow);

        // Check whether the length and name properties are still present as
        // AccessorInfo objects. In that case, their value can be recomputed even if
        // the actual value on the object changes.
        Comment("Check name and length properties");
        {
            const int length_index = JSFunction::kLengthDescriptorIndex;
            TNode<Name> maybe_length = LoadKeyByDescriptorEntry(descriptors, length_index);
            GotoIf(WordNotEqual(maybe_length, LoadRoot(RootIndex::klength_string)),
                &slow);

            TNode<Object> maybe_length_accessor = LoadValueByDescriptorEntry(descriptors, length_index);
            GotoIf(TaggedIsSmi(maybe_length_accessor), &slow);
            Node* length_value_map = LoadMap(CAST(maybe_length_accessor));
            GotoIfNot(IsAccessorInfoMap(length_value_map), &slow);

            const int name_index = JSFunction::kNameDescriptorIndex;
            TNode<Name> maybe_name = LoadKeyByDescriptorEntry(descriptors, name_index);
            GotoIf(WordNotEqual(maybe_name, LoadRoot(RootIndex::kname_string)), &slow);

            TNode<Object> maybe_name_accessor = LoadValueByDescriptorEntry(descriptors, name_index);
            GotoIf(TaggedIsSmi(maybe_name_accessor), &slow);
            TNode<Map> name_value_map = LoadMap(CAST(maybe_name_accessor));
            GotoIfNot(IsAccessorInfoMap(name_value_map), &slow);
        }

        // Choose the right bound function map based on whether the target is
        // constructable.
        Comment("Choose the right bound function map");
        VARIABLE(bound_function_map, MachineRepresentation::kTagged);
        {
            Label with_constructor(this);
            VariableList vars({ &bound_function_map }, zone());
            Node* native_context = LoadNativeContext(context);

            Label map_done(this, vars);
            GotoIf(IsConstructorMap(receiver_map), &with_constructor);

            bound_function_map.Bind(LoadContextElement(
                native_context, Context::BOUND_FUNCTION_WITHOUT_CONSTRUCTOR_MAP_INDEX));
            Goto(&map_done);

            BIND(&with_constructor);
            bound_function_map.Bind(LoadContextElement(
                native_context, Context::BOUND_FUNCTION_WITH_CONSTRUCTOR_MAP_INDEX));
            Goto(&map_done);

            BIND(&map_done);
        }

        // Verify that __proto__ matches that of a the target bound function.
        Comment("Verify that __proto__ matches target bound function");
        Node* prototype = LoadMapPrototype(receiver_map);
        Node* expected_prototype = LoadMapPrototype(bound_function_map.value());
        GotoIf(WordNotEqual(prototype, expected_prototype), &slow);

        // Allocate the arguments array.
        Comment("Allocate the arguments array");
        VARIABLE(argument_array, MachineRepresentation::kTagged);
        {
            Label empty_arguments(this);
            Label arguments_done(this, &argument_array);
            GotoIf(Uint32LessThanOrEqual(argc, Int32Constant(1)), &empty_arguments);
            TNode<IntPtrT> elements_length = Signed(ChangeUint32ToWord(Unsigned(Int32Sub(argc, Int32Constant(1)))));
            TNode<FixedArray> elements = CAST(AllocateFixedArray(
                PACKED_ELEMENTS, elements_length, kAllowLargeObjectAllocation));
            VARIABLE(index, MachineType::PointerRepresentation());
            index.Bind(IntPtrConstant(0));
            VariableList foreach_vars({ &index }, zone());
            args.ForEach(
                foreach_vars,
                [this, elements, &index](Node* arg) {
                    StoreFixedArrayElement(elements, index.value(), arg);
                    Increment(&index);
                },
                IntPtrConstant(1));
            argument_array.Bind(elements);
            Goto(&arguments_done);

            BIND(&empty_arguments);
            argument_array.Bind(EmptyFixedArrayConstant());
            Goto(&arguments_done);

            BIND(&arguments_done);
        }

        // Determine bound receiver.
        Comment("Determine bound receiver");
        VARIABLE(bound_receiver, MachineRepresentation::kTagged);
        {
            Label has_receiver(this);
            Label receiver_done(this, &bound_receiver);
            GotoIf(Word32NotEqual(argc, Int32Constant(0)), &has_receiver);
            bound_receiver.Bind(UndefinedConstant());
            Goto(&receiver_done);

            BIND(&has_receiver);
            bound_receiver.Bind(args.AtIndex(0));
            Goto(&receiver_done);

            BIND(&receiver_done);
        }

        // Allocate the resulting bound function.
        Comment("Allocate the resulting bound function");
        {
            Node* bound_function = Allocate(JSBoundFunction::kSize);
            StoreMapNoWriteBarrier(bound_function, bound_function_map.value());
            StoreObjectFieldNoWriteBarrier(
                bound_function, JSBoundFunction::kBoundTargetFunctionOffset, receiver);
            StoreObjectFieldNoWriteBarrier(bound_function,
                JSBoundFunction::kBoundThisOffset,
                bound_receiver.value());
            StoreObjectFieldNoWriteBarrier(bound_function,
                JSBoundFunction::kBoundArgumentsOffset,
                argument_array.value());
            Node* empty_fixed_array = EmptyFixedArrayConstant();
            StoreObjectFieldNoWriteBarrier(
                bound_function, JSObject::kPropertiesOrHashOffset, empty_fixed_array);
            StoreObjectFieldNoWriteBarrier(bound_function, JSObject::kElementsOffset,
                empty_fixed_array);

            args.PopAndReturn(bound_function);
        }

        BIND(&slow);
        {
            // We are not using Parameter(Descriptor::kJSTarget) and loading the value
            // from the current frame here in order to reduce register pressure on the
            // fast path.
            TNode<JSFunction> target = LoadTargetFromFrame();
            TailCallBuiltin(Builtins::kFunctionPrototypeBind, context, target,
                new_target, argc);
        }
    }

    // ES6 #sec-function.prototype-@@hasinstance
    TF_BUILTIN(FunctionPrototypeHasInstance, CodeStubAssembler)
    {
        Node* context = Parameter(Descriptor::kContext);
        Node* f = Parameter(Descriptor::kReceiver);
        Node* v = Parameter(Descriptor::kV);
        Node* result = OrdinaryHasInstance(context, f, v);
        Return(result);
    }

} // namespace internal
} // namespace v8
